package repository

import (
	"context"
	"time"
	"webook/internal/domain"
	"webook/internal/repository/cache"
	"webook/internal/repository/dao"
)

var (
	ErrUserDuplicate       = dao.ErrUserDuplicate
	ErrUserEmailNotExists  = dao.ErrUserEmailNotExists
	ErrUserPhoneNotExists  = dao.ErrUserPhoneNotExists
	ErrUserOpenIDNotExists = dao.ErrUserOpenIDNotExists
)

type UserRepository interface {
	Create(ctx context.Context, u domain.User) error
	FindByEmail(ctx context.Context, email string) (domain.User, error)
	UpdateById(ctx context.Context, user domain.User) error
	FindById(ctx context.Context, id int64) (domain.User, error)
	FindByPhone(ctx context.Context, phone string) (domain.User, error)
	FindByWeChat(ctx context.Context, info domain.WeChatInfo) (domain.User, error)
}

type CacheUserRepository struct {
	dao   dao.UserDAO
	cache cache.UserCache
}

func NewUserRepository(dao dao.UserDAO, cache cache.UserCache) UserRepository {
	return &CacheUserRepository{
		dao:   dao,
		cache: cache,
	}
}

func (repo *CacheUserRepository) Create(ctx context.Context, u domain.User) error {
	err := repo.dao.Insert(ctx, dao.User{
		Id:       u.Id,
		Email:    u.Email,
		Password: u.Password,
		Phone:    u.Phone,
	})
	if err == ErrUserDuplicate {
		return ErrUserDuplicate
	}
	return err
}

func (repo *CacheUserRepository) FindByEmail(ctx context.Context, email string) (domain.User, error) {
	u, err := repo.dao.FindByEmail(ctx, email)
	if err == ErrUserEmailNotExists {
		return domain.User{}, ErrUserEmailNotExists
	}
	return domain.User{
		Id:       u.Id,
		Email:    u.Email,
		Password: u.Password,
	}, nil
}

func (repo *CacheUserRepository) UpdateById(ctx context.Context, user domain.User) error {
	return repo.dao.UpdateById(ctx, dao.User{
		Id:       user.Id,
		Nickname: user.Nickname,
		Birthday: user.Birthday.UnixMilli(),
		Aboutme:  user.AboutMe,
	})
}

func (repo *CacheUserRepository) FindById(ctx context.Context, id int64) (domain.User, error) {
	// 1. 先从缓存中找
	user, err := repo.cache.Get(ctx, id)
	// 1.1 没报错怎么办
	if err == nil {
		return user, nil
	}
	// 1.2 报错但是redis返回的是键不存在怎么办
	if err == cache.ErrKeyNotExist {
		// 2. 找不到从DB中找
		user, err := repo.dao.FindById(ctx, id)
		if err != nil {
			return domain.User{}, err
		}
		// 3. 找到之后回写缓存（忽略错误）
		repo.cache.Set(ctx, domain.User{
			Id:       user.Id,
			Nickname: user.Nickname,
			Email:    user.Email,
			Password: user.Password,
			Birthday: time.UnixMilli(user.Birthday),
			AboutMe:  user.Aboutme,
			Ctime:    time.UnixMilli(user.Ctime),
		})
		return domain.User{
			Id:       user.Id,
			Email:    user.Email,
			Password: user.Password,
			Nickname: user.Nickname,
			Birthday: time.UnixMilli(user.Birthday),
			AboutMe:  user.Aboutme,
		}, nil
	}
	// 1.3 报错但是redis异常怎么办
	// TODO: 推荐做法: 继续查数据库（防止缓存击穿、穿透等问题）
	return domain.User{}, err
}

func (repo *CacheUserRepository) FindByPhone(ctx context.Context, phone string) (domain.User, error) {
	u, err := repo.dao.FindByPhone(ctx, phone)
	if err == ErrUserPhoneNotExists {
		return domain.User{}, ErrUserPhoneNotExists
	}
	return domain.User{
		Id: u.Id,
	}, nil
}

func (repo *CacheUserRepository) FindByWeChat(ctx context.Context, info domain.WeChatInfo) (domain.User, error) {
	u, err := repo.dao.FindByOpenID(ctx, info.OpenId)
	if err == ErrUserOpenIDNotExists {
		return domain.User{}, ErrUserOpenIDNotExists
	}
	return domain.User{
		Id: u.Id,
	}, nil
}
